import json
import os
import random
import sys
import time
from typing import Tuple

import requests
from loguru import logger
from werobot import WeRoBot
import socket

from mdtool.config_reader import Config
from mdtool.entity.wx_entity import WxArticle, WxArticleList, WxImageList

STABLE_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/stable_token"
IMAGE_UPLOAD_URL = (
    "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={}&type=image"
)
IMAGES_LIST_URL = (
    "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token={}"
)
ARTICLE_UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/draft/add?access_token={}"
HEADER = {"Content-type": "text/plain; charset=utf-8"}
JSON_HEADER = {"Content-Type": "application/json; charset=utf-8"}
ARTICLE_LATEST_URL = "https://mp.weixin.qq.com/cgi-bin/appmsgpublish?sub=list&begin={}&count={}&token={}&lang=zh_CN&f=json"


def _list_from_dict(result: dict) -> WxArticleList:
    """
    将微信接口返回的文章列表字典，转换为 WxArticleList 对象
    """
    articles = []
    for item in result.get("item", []):
        content = item.get("content", {})
        news_items = content.get("news_item", [])
        for news in news_items:
            article = WxArticle(
                media_id=item.get("media_id", ""),
                title=news.get("title", ""),
                author=news.get("author", ""),
                digest=news.get("digest", ""),
                show_cover_pic=news.get("show_cover_pic", 1),
                content=news.get("content", ""),
                content_source_url=news.get("content_source_url", ""),
                need_open_comment=news.get("need_open_comment", 1),
                thumb_media_id=news.get("thumb_media_id", ""),
                article_subject_id=news.get("article_type", ""),
                # 下面两个字段可能在 news 或 content 里
                create_time=content.get("create_time", -1),
                update_time=content.get("update_time", -1),
            )
            articles.append(article)
    return WxArticleList(
        total_count=result.get("total_count", 0),
        item_count=result.get("item_count", 0),
        item=articles,
    )


class WxClient:

    def __init__(self, config: Config):
        self.config = config
        # default domain 是 WECHAT
        domain = self.config.get(Config.DOMAIN_NAME, "WECHAT")
        self.robot = WeRoBot()
        self.robot.config["APP_ID"] = os.environ[domain + "_APP_ID"]
        self.robot.config["APP_SECRET"] = os.environ[domain + "_APP_SECRET"]
        self.sender = self.robot.client
        logger.debug(f"['APP_ID']: {self.robot.config['APP_ID']}")
        logger.debug(f"['APP_SECRET']: {self.robot.config['APP_SECRET']}")

    def _extract_ip_from_error(self, error_msg: str) -> str:
        """从错误信息中提取IP地址"""
        try:
            # 从异常信息中提取IP地址
            # 异常格式: "40164: invalid ip 123.119.97.183 ipv6 ::ffff:123.119.97.183, not in whitelist"
            if "invalid ip" in error_msg.lower():
                # 查找IP地址模式
                import re
                ip_pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
                match = re.search(ip_pattern, error_msg)
                if match:
                    return match.group(1)
        except Exception:
            pass
        return "未知IP"

    def get_access_token(self):
        try:
            return self.sender.get_access_token()
        except Exception as e:
            error_msg = str(e)
            if "40164" in error_msg and "invalid ip" in error_msg.lower():
                extracted_ip = self._extract_ip_from_error(error_msg)
                error_message = f"""
错误: 无法获取微信公众号访问令牌 (access token)

原因: 当前IP地址 {extracted_ip} 不在微信公众号平台的白名单中

解决方案:
1. 登录微信公众号平台 (https://mp.weixin.qq.com/)
2. 进入 "设置与开发" -> "基本配置"
3. 在 "IP白名单" 中添加以下IP地址:
   {extracted_ip}
4. 保存配置后重新运行程序

如果问题仍然存在，请联系微信公众号平台客服。
"""
                print(error_message)
                logger.error(f"IP白名单错误: {extracted_ip} 不在白名单中")
                sys.exit(1)
            else:
                # 其他类型的错误，也提示用户并退出程序
                error_message = f"""
错误: 无法获取微信公众号访问令牌 (access token)

原因: {error_msg}

解决方案:
1. 检查微信公众号的 APP_ID 和 APP_SECRET 是否正确
2. 确认微信公众号平台服务是否正常
3. 检查网络连接是否正常
4. 如果问题持续存在，请联系微信公众号平台客服

程序将退出。
"""
                print(error_message)
                logger.error(f"获取access token失败: {e}")
                sys.exit(1)

    def get_stable_token(self):
        url = STABLE_TOKEN_URL
        datas = {
            "grant_type": "client_credential",
            "appid": self.robot.config["APP_ID"],
            "secret": self.robot.config["APP_SECRET"],
            "force_refresh": False,
        }
        response = requests.post(
            url,
            data=json.dumps(datas, ensure_ascii=False).encode("utf-8"),
            headers=JSON_HEADER,
        )
        if response.status_code != 200:
            logger.debug(f"请求失败，状态码: {response.status_code}")
            return ""
        ret = response.json()
        print(ret)
        return ret["access_token"]

    def _get_image_type(self, file_path) -> str:
        # 从文件扩展名得到图片类型
        return "image/" + file_path.split(".")[-1]

    def upload_permanent_media(self, file_path, file_name) -> Tuple[str, str]:
        token = self.get_access_token()
        print("token:" + token)
        url = IMAGE_UPLOAD_URL.format(token)
        files = {
            "media": (file_name, open(file_path, "rb"), self._get_image_type(file_path))
        }
        response = requests.post(url, files=files)
        print(response)
        if response.status_code != 200:
            logger.error(f"请求失败，状态码: {response.status_code}")
            raise Exception(f"请求失败，状态码: {response.status_code}")
        ret_json = response.json()
        media_id, media_url = None, None
        try:
            media_id = ret_json["media_id"]
            media_url = ret_json["url"]
            return media_id, media_url
        except Exception as e:
            logger.error(e)
            sys.exit(1)

    def count_of_images(self) -> int:
        ret = self.get_images_by_offset(0, 1)
        return ret.total_count

    def get_images_by_offset(self, offset=0, count=20) -> WxImageList:
        token = self.get_access_token()
        url = IMAGES_LIST_URL.format(token)
        data = {
            "type": "image",
            "offset": offset,
            "count": count,
        }
        response = requests.post(
            url,
            data=json.dumps(data, ensure_ascii=False).encode("utf-8"),
            headers=JSON_HEADER,
        )
        if response.status_code != 200:
            logger.debug(f"请求失败，状态码: {response.status_code}")
            return WxImageList.ERROR_LIST
        ret = response.json()
        return WxImageList.fromJson(ret)

    def get_image_list(self):
        total = self.count_of_images()
        if total == -1:
            return WxImageList.EMPTY_LIST
        offset = 0
        count = 20
        result = None
        while offset < total:
            delay = random.uniform(1, 3)
            time.sleep(delay)  # 随机停 1 到 3 秒，防止 WX 封禁 API调用
            ret = self.get_images_by_offset(offset, count)
            if result is None:
                result = ret
            else:
                result.merge(ret)
            offset += count
        return result

    def get_published_articles(self, offset=0, count=20) -> WxArticleList:
        "得到已发布的文章列表。"
        # result = self.robot.client.get_media_list("news", offset=offset, count=count)
        token = self.get_stable_token()
        if not token:
            logger.debug("获取 access_token 失败")
            return WxArticleList.ERROR  # 或者返回一个错误提示
        url = ARTICLE_LATEST_URL.format(offset, count, token)
        result = self.robot.client.get(url)
        if result["base_resp"]["ret"] != 0:
            logger.debug(f"出错了！ ARTICLE_LATEST_URL: {url}")
            logger.debug(result)
            return WxArticleList.ERROR
        artlist = _list_from_dict(result)
        logger.debug(
            f"最新发布的文章列表: offset={offset}count={count}\t" + artlist.to_json()
        )
        return artlist

    def upload_article_draft(self, articles: list) -> str | None:
        token = self.get_access_token()

        datas = json.dumps({"articles": articles}, ensure_ascii=False).encode("utf-8")
        r = requests.post(
            ARTICLE_UPLOAD_URL.format(token), data=datas, headers=JSON_HEADER
        )
        if r.status_code != 200:
            logger.warning("Failed to upload, status_code: {}".format(r.status_code))
            logger.debug(r.text + "\n" + str(articles))
            return None
        resp = json.loads(r.text)
        print(resp)
        try:
            media_id = resp["media_id"]
            logger.info("Successfully uploaded, media_id: {}".format(media_id))
            return media_id
        except Exception:
            logger.warning(r.text)
            return None


if __name__ == "__main__":
    config = Config({"domain": {"type": "wechat", "name": "CD20"}})
    client = WxClient(config)
    print(client.robot.client.get_media_list("news", 184, 1))
